﻿/*
 * Author: Duncan Jenkins / Martijn Koopman / Roy Scheefhals / Kasper van Voorst
 * Individual pieces of code have their author named.
 * 
 * Description: Base class for all our Vision based image functions
 * */

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Collections.Generic;

namespace LibraryCS
{

    /// <summary>
    /// Basic image class that initializes channels, crops, scales etc.
    /// Extends around the Bitmap image class
    /// </summary>
    public class VImage
    {

        #region private variables

        private byte[] _GrayPixels;

        private byte[] _rgb;

        #endregion

        #region public variables

        /// <summary>
        /// Red channel of the input bitmap
        /// </summary>

        public byte[] RedPixels;
        /// <summary>
        /// Green channel of the input bitmap
        /// </summary>
        public byte[] GreenPixels;

        /// <summary>
        /// Blue channel of the input bitmap
        /// </summary>
        public byte[] BluePixels;

        /// <summary>
        /// array of the image in HSV format
        /// <para>Call Function calculateHSV() first to initialize first</para>
        /// </summary>
        public double[][] HSV;

        /// <summary>
        /// predefined HSL int array, no function made that fills it up yet!
        /// </summary>
        public int[][] HSL;

        public int Format;
        public Bitmap localBitmap;

        public int Width, Height;

        public Blob blob;

        public bool hasHSV = false;
        public bool hasGray = false;
        public bool hasRGB = false;

        #endregion 

        #region public properties

        /// <summary>
        /// Graypixels of the input image
        /// </summary>
        public byte[] GrayPixels
        {
            get
            {
                if (!hasGray)
                {
                    fillGray();
                }
                return _GrayPixels;
            }
            set
            {
                _GrayPixels = value;
                hasGray = true;
            }
        }
        /// <summary>
        /// One array with all rgb values
        /// </summary>
        public byte[] rgb
        {
            get
            {
                if (!hasRGB)
                {
                    fillRGB();
                }
                return _rgb;
            }
        }

        #endregion
        /*
         * Constructor written by: Martijn Koopman
         * */
        /// <summary>
        /// Creates a empty VImage object with all values and images reset ( black )
        /// </summary>
        /// <param name="Width">Width of the image</param>
        /// <param name="Height">Height of the image</param>
        public VImage(int Width, int Height)
        {
            this.Width = Width;
            this.Height = Height;

            RedPixels = new byte[Width * Height];
            GreenPixels = new byte[Width * Height];
            BluePixels = new byte[Width * Height];
            localBitmap = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppRgb);
            Format = 4;
        }

        /*
         * Constructor written by: Duncan Jenkins
         * */
        /// <summary>
        /// Constructor that converts the input image to the VImage class
        /// <para>this constructor already fills the RGB channel and local image</para>
        /// </summary>
        /// <param name="image">Original bitmap image to be used in the VImage object</param>
        public VImage(Bitmap image)
        {
            localBitmap = image;
            Width = image.Width;
            Height = image.Height;

            // Lock image, loop through it once, filling the ARGB arrays
            RedPixels = new byte[Width * Height];
            GreenPixels = new byte[Width * Height];
            BluePixels = new byte[Width * Height];

            // Decide pixelformat
            if (image.PixelFormat == PixelFormat.Format24bppRgb)
            {
                Format = 3;
            }
            else if (image.PixelFormat == PixelFormat.Format32bppArgb || image.PixelFormat == PixelFormat.Format32bppRgb)
            {
                Format = 4;
            }
            else
            {
                throw new Exception("The image you are trying to use has an unsupported PixelFormat");
            }

            BitmapData source = image.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, image.PixelFormat);
            unsafe
            {
                byte* sourcePtr = (byte*)source.Scan0;
                int bytesToCheck = source.Stride * Height;
                if (Format == 4)
                {
                    for (int i = 0; i < bytesToCheck; i += 4)
                    {
                        int pixel = i / 4;
                        // Process pixels of entered image
                        //byte alpha = sourcePtr[i + 3]; 
                        byte red = sourcePtr[i + 2];
                        byte green = sourcePtr[i + 1];
                        byte blue = sourcePtr[i];

                        // Fill RGB array
                        RedPixels[pixel] = red;
                        GreenPixels[pixel] = green;
                        BluePixels[pixel] = blue;
                    }
                }
                else
                {
                    int padding = source.Stride - Width * 3;
                    bytesToCheck = Width * 3 * Height;
                    for (int i = 0; i < bytesToCheck; i += 3)
                    {
                        if (i != 0 && i % Width * 3 == 0)
                        {
                            i += padding;
                        }
                        int pixel = i / 3;
                        // Process pixels of entered image
                        byte red = sourcePtr[i + 2];
                        byte green = sourcePtr[i + 1];
                        byte blue = sourcePtr[i];

                        // Fill RGB array
                        RedPixels[pixel] = red;
                        GreenPixels[pixel] = green;
                        BluePixels[pixel] = blue;

                    }
                }
            }
            image.UnlockBits(source);
        }

        /// <summary>
        /// Copy constructor for the VImage class
        /// </summary>
        /// <param name="oldVImage">Source VImage object to copy the info from</param>
        public VImage(VImage oldVImage)
        {
            RedPixels = oldVImage.RedPixels;
            GreenPixels = oldVImage.GreenPixels;
            BluePixels = oldVImage.BluePixels;

            localBitmap = oldVImage.localBitmap;
            Width = oldVImage.Width;
            Height = oldVImage.Height;
            Format = oldVImage.Format;

            blob = oldVImage.blob;
        }
        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Creates grayscaled histogram of the image using the RGB channels
        /// </summary>
        /// <param name="area">area of the image that has to be in the histogram</param>
        /// <param name="bins">amount of bins in the histogram</param>
        /// <param name="normalized">true if the histogram has to be normalized</param>
        /// <param name="output">path + name where to save the histogram as CSV file
        ///                      <para>set to null if not to save the histogram</para></param>
        /// <returns>Array of arrays of the histogram
        ///          <para>the position histogram[0][i] contains the gray bins </para>
        ///          <para>where (i = 0, i = 1, ... i = bins -1 )</para> </returns>
        public double[][] createHistogramGray(Rectangle area, int bins, bool normalize, String output)
        {
            int regionLength = area.Width * area.Height;
            int areaY = area.Y;
            int areaX = area.X;
            int areaHeight = area.Height;
            int areaWidth = area.Width;
            double[][] histogram = new double[3][];
            histogram[0] = new double[bins];

            // Loop through the imageArray
            for (int y = 0; y < areaHeight; y++)
            {
                for (int x = 0; x < areaWidth; x++)
                {
                    int index = (areaY + y) * Width + (areaX + x);
                    double grayValue = (RedPixels[index] + GreenPixels[index] + BluePixels[index]) / 3;
                    int intensity = (int)((grayValue * bins) / 256);
                    histogram[0][intensity]++;
                }
            }

            if (normalize)
            {
                // Normalize values
                for (int i = 0; i < bins; i++)
                {
                    histogram[0][i] /= regionLength;
                }
            }

            if (output != null)
            {
                // Save file as a CSV file

                // Delete output file if it already exists
                if (File.Exists(output))
                {
                    File.Delete(output);
                }

                StreamWriter sw = new StreamWriter(output, true);
                double total = 0;

                sw.WriteLine("bin,density");

                for (int i = 0; i < bins; i++)
                {
                    String line = i + "," + histogram[0][i];
                    total += histogram[0][i];
                    sw.WriteLine(line);
                }

                sw.WriteLine("Total:," + total);
                sw.Close();
            }

            return histogram;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Creates histogram of the 3 RGB channels of the image
        /// </summary>
        /// <param name="area">area of the image that has to be in the histogram</param>
        /// <param name="bins">amount of bins in the histogram</param>
        /// <param name="normalized">set to true if the histogram has to be normalized</param>
        /// <param name="output">path + name where to save the histogram as CSV file
        ///                      <para>set to null if not to save the histogram</para></param>
        /// <returns>array of arrays containing the 3 RGB histograms
        ///          <para>position histogram[0][i] contains the Red channel</para>
        ///          <para>position histogram[1][i] contains the Green channel</para>
        ///          <para>position histogram[2][i] contains the Blue channel</para>
        ///          <para>where ( i = 0, i = 1, ... i = bins -1)</para></returns>
        public double[][] createHistogramRGB(Rectangle area, int bins, bool normalized, String output)
        {
            int regionLength = area.Width * area.Height;
            int areaY = area.Y;
            int areaX = area.X;
            int areaHeight = area.Height;
            int areaWidth = area.Width;
            double[][] histogram = new double[3][];
            histogram[0] = new double[bins];
            histogram[1] = new double[bins];
            histogram[2] = new double[bins];

            for (int y = 0; y < areaHeight; y++)
            {
                for (int x = 0; x < areaWidth; x++)
                {
                    int index = (areaY + y) * Width + (areaX + x);
                    int intensityR = (RedPixels[index] * bins) / 256;
                    int intensityG = (GreenPixels[index] * bins) / 256;
                    int intensityB = (BluePixels[index] * bins) / 256;
                    histogram[0][intensityR]++;
                    histogram[1][intensityG]++;
                    histogram[2][intensityB]++;
                }
            }

            if (normalized)
            {
                // Normalize values
                for (int i = 0; i < bins; i++)
                {
                    histogram[0][i] /= regionLength;
                    histogram[1][i] /= regionLength;
                    histogram[2][i] /= regionLength;
                }
            }

            if (output != null)
            {
                // Save file as a CSV file

                // Delete output file if it already exists
                if (File.Exists(output))
                {
                    File.Delete(output);
                }

                StreamWriter sw = new StreamWriter(output, true);
                double totalR = 0, totalG = 0, totalB = 0;

                sw.WriteLine("bin,density,,bin;density,,bin;density");

                for (int i = 0; i < bins; i++)
                {
                    String line = i + "," + histogram[0][i] + ",," + i + "," + histogram[1][i] + ",," + i + "," + histogram[2][i];
                    totalR += histogram[0][i];
                    totalG += histogram[1][i];
                    totalB += histogram[2][i];
                    sw.WriteLine(line);
                }

                sw.WriteLine("Total:," + totalR + ",,Total:," + totalG + ",,Total:," + totalB);
                sw.Close();
            }

            return histogram;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Creates histogram of the 3 RGB channels of the image
        /// </summary>
        /// <param name="area">area of the image that has to be in the histogram</param>
        /// <param name="bins">amount of bins in the histogram</param>
        /// <param name="normalized">set to true if the histogram has to be normalized</param>
        /// <param name="output">path + name where to save the histogram as CSV file
        ///                      <para>set to null if not to save the histogram</para></param>
        /// <returns>array of arrays containing the 3 RGB histograms
        ///          <para>position histogram[0][i] contains the Hue channel</para>
        ///          <para>position histogram[1][i] contains the Saturation channel</para>
        ///          <para>position histogram[2][i] contains the Value channel</para>
        ///          <para>where ( i = 0, i = 1, ... i = bins -1)</para></returns>
        public double[][] createHistogramHSV(Rectangle area, int bins, bool normalized, String output)
        {
            if (!hasHSV)
            {
                calculateHSV();
            }

            int regionLength = area.Width * area.Height;
            int areaY = area.Y;
            int areaX = area.X;
            int areaHeight = area.Height;
            int areaWidth = area.Width;
            double[][] histogram = new double[3][];
            histogram[0] = new double[bins];
            histogram[1] = new double[bins];
            histogram[2] = new double[bins];

            for (int y = 0; y < areaHeight; y++)
            {
                for (int x = 0; x < areaWidth; x++)
                {
                    int index = (areaY + y) * Width + (areaX + x);
                    int intensityH = (int)((HSV[index][0] * bins) / 360);
                    int intensityS = (int)((HSV[index][1] * bins) / 256);
                    int intensityV = (int)((HSV[index][2] * bins) / 256);
                    histogram[0][intensityH]++;
                    histogram[1][intensityS]++;
                    histogram[2][intensityV]++;
                }
            }

            if (normalized)
            {
                // Normalize values
                for (int i = 0; i < bins; i++)
                {
                    histogram[0][i] /= regionLength;
                    histogram[1][i] /= regionLength;
                    histogram[2][i] /= regionLength;
                }
            }

            if (output != null)
            {
                // Save file as a CSV file

                // Delete output file if it already exists
                if (File.Exists(output))
                {
                    File.Delete(output);
                }

                StreamWriter sw = new StreamWriter(output, true);
                double totalH = 0, totalS = 0, totalV = 0;

                sw.WriteLine("bin,density,,bin;density,,bin;density");

                for (int i = 0; i < bins; i++)
                {
                    String line = i + "," + histogram[0][i] + ",," + i + "," + histogram[1][i] + ",," + i + "," + histogram[2][i];
                    totalH += histogram[0][i];
                    totalS += histogram[1][i];
                    totalV += histogram[2][i];
                    sw.WriteLine(line);
                }

                sw.WriteLine("Total:," + totalH + ",,Total:," + totalS + ",,Total:," + totalV);
                sw.Close();
            }

            return histogram;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Function fills the graychannel using the RGB channels 
        /// </summary>
        public void fillGray()
        {
            _GrayPixels = new byte[Width * Height];

            // Fill the Gray array
            for (int i = 0; i < RedPixels.Length; i++)
            {
                _GrayPixels[i] = (byte)((RedPixels[i] + GreenPixels[i] + BluePixels[i]) / 3);
            }

            hasGray = true;
        }

        /*
         * Function written by: Kasper van Voorst
         * */
        /// <summary>
        /// Function fills the RGB array using all channels
        /// </summary>
        public void fillRGB()
        {
            _rgb = new byte[localBitmap.Width * localBitmap.Height * 4];
            int x = 0;
            for (int i = 0; i < _rgb.Length; i += 4)
            {
                
                _rgb[i]     = BluePixels[x];
                _rgb[i + 1] = GreenPixels[x];
                _rgb[i + 2] = RedPixels[x];
                _rgb[i + 3] = 255;

                x++;
            }
            hasRGB = true;
        }
        /*
         * Function written by: Martijn Koopman (adapted by: Duncan Jenkins)
         * */
        /// <summary>
        /// <para>fills the HSV array of arrays using the RGB channels using the format:</para>
        /// <para>imagehsv[i][0, 1 or 2]</para>
        /// <para>i stands for any pixel point with the range i = 0 .... i = Width * Height.</para>
        /// <para>0 stands for the Hue</para>
        /// <para>1 stands for the Saturation</para>
        /// <para>2 stands for the Value</para>
        /// </summary>
        public void calculateHSV()
        {
            HSV = new double[Width * Height][];

            // Calculate HSV values
            for (int i = 0; i < RedPixels.Length; i++)
            {
                HSV[i] = new double[3];

                int red = RedPixels[i];
                int green = GreenPixels[i];
                int blue = BluePixels[i];

                double min = Math.Min(Math.Min(red, green), blue);
                double max = Math.Max(Math.Max(red, green), blue);
                double delta = max - min;

                double hue = 0.0;
                double sat = 0.0;
                double val = 0.0;

                // Calculate Value
                val = max / 255;

                // Calculate Saturation
                if (max == 0)
                {
                    sat = 0.0;
                }
                else
                {
                    sat = delta / max;
                }

                // Calculate Hue
                if (min == max)
                {
                    hue = 0.0;
                }
                else
                {
                    if (red == max)
                    {
                        // Between yellow & magenta
                        hue = (green - blue) / delta;
                    }
                    else if (green == max)
                    {
                        // Between cyan & yellow
                        hue = 2 + (blue - red) / delta;
                    }
                    else
                    {
                        // Between magenta & cyan
                        hue = 4 + (red - green) / delta;
                    }

                    // Degrees
                    hue *= 60;

                    if (hue < 0)
                    {
                        hue += 360;
                    }

                    hue %= 360;
                }

                // Fill HSV array
                HSV[i][0] = hue;
                HSV[i][1] = sat;
                HSV[i][2] = val;
            }

            hasHSV = true;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Creates new bitmap containing only the specified channel
        /// </summary>
        /// <param name="channel">channel "R", "G" or "B"</param>
        /// <returns>Bitmap containing only the specified channed, other 2 channels are set to 0</returns>
        public Bitmap getSingleChannel(String channel)
        {
            // Create a blank bitmap
            Bitmap image = new Bitmap(localBitmap.Width, localBitmap.Height, PixelFormat.Format32bppArgb);
            BitmapData destination = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.WriteOnly, image.PixelFormat);

            unsafe
            {
                int* destPtr = (int*)destination.Scan0;
                for (int i = 0; i < RedPixels.Length; i++)
                {
                    int color = 0;

                    if (channel.ToLower() == "r")
                    {
                        color = (int)(RedPixels[i] << 16);
                    }
                    else if (channel.ToLower() == "g")
                    {
                        color = (int)(GreenPixels[i] << 8);
                    }
                    else
                    {
                        color = (int)(BluePixels[i]);
                    }

                    destPtr[i] = 255 << 24 | color;
                }
            }

            image.UnlockBits(destination);
            return image;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        public Bitmap getHueRange(double start, double end)
        {
            if (!hasHSV)
            {
                calculateHSV();
            }

            Bitmap image = new Bitmap(localBitmap.Width, localBitmap.Height, PixelFormat.Format32bppArgb);
            BitmapData destination = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.WriteOnly, image.PixelFormat);

            unsafe
            {
                int* destPtr = (int*)destination.Scan0;
                for (int i = 0; i < HSV.Length; i++)
                {
                    int x = i % Width;
                    int y = i / Width;
                    if (HSV[i][0] > start && HSV[i][0] < end)
                    {
                        // Print in color
                        destPtr[y * Width + x] = 255 << 24 | (int)RedPixels[i] << 16 | (int)GreenPixels[i] << 8 | (int)BluePixels[i];
                    }
                    else
                    {
                        // Otherwise, print it in gray
                        //destPtr[y * Width + x] = 255 << 24 | gray << 16 | gray << 8 | gray;
                        destPtr[y * Width + x] = 255 << 24 | 0;
                    }
                }
            }

            image.UnlockBits(destination);
            return image;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Get a singel pixel from the RGB arrays in ARGB format
        /// </summary>
        /// <param name="x">x position of pixel</param>
        /// <param name="y">y position of pixel</param>
        /// <returns>Pixel in ARGB format</returns>
        public int GetSinglePixel(int x, int y)
        {
            int index = y * localBitmap.Width + x;
            return 255 << 24 | RedPixels[index] << 16 | GreenPixels[index] << 8 | BluePixels[index];
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// <para>Set pixel in RGB arrays AND the LocalImage</para>
        /// <para>Dont use this function often.</para>
        /// <para>this function will lock the bits every time to save the new pixel</para>
        /// </summary>
        /// <param name="x">x position of pixel</param>
        /// <param name="y">y position of pixel</param>
        /// <param name="value">color value of pixel in ARGB format</param>
        public void SetSinglePixel(int x, int y, int value)
        {
            int index = y * localBitmap.Width + x;
            RedPixels[index] = (byte)((value >> 16) & 255);
            GreenPixels[index] = (byte)((value >> 8) & 255);
            BluePixels[index] = (byte)(value & 255);

            BitmapData destination = localBitmap.LockBits(new Rectangle(0, 0, localBitmap.Width, localBitmap.Height), ImageLockMode.WriteOnly, localBitmap.PixelFormat);

            unsafe
            {
                int* destPtr = (int*)destination.Scan0;
                destPtr[y * localBitmap.Width + x] = value;
            }

            localBitmap.UnlockBits(destination);
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// Gets a single array of the RGB values ( includes the Alpha value )
        /// </summary>
        /// <returns>Int Array with the ARGB format</returns>
        public int[] GetPixelsRGB()
        {
            int[] newArray = new int[Width * Height];
            for (int i = 0; i < RedPixels.Length; i++)
            {
                newArray[i] = 255 << 24 | RedPixels[i] << 16 | GreenPixels[i] << 8 | BluePixels[i];
            }
            return newArray;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// get HSV double array of the RGB arrays
        /// </summary>
        /// <returns>array of double arrays containing </returns>
        public double[][] GetPixelsHSV()
        {
            if (!hasHSV)
            {
                calculateHSV();
            }
            return HSV;
        }

        /// <summary>
        /// Get a bitmap of this image
        /// </summary>
        /// <returns>A bitmap</returns>
        public Bitmap getBitmap() {
            ApplyPixelChanges();
            return localBitmap;
        }

        /*
         * Function written by: Duncan Jenkins
         * */

        /// <summary>
        /// Loads the 3 byte arrays RGB into the local image
        /// </summary>
        /// <returns>The Current object</returns>
        public VImage ApplyPixelChanges()
        {
            // Insert current ARGB array into picture
            BitmapData destination = localBitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, localBitmap.PixelFormat);

            unsafe
            {
                byte* destPtr = (byte*)destination.Scan0;
                int bytesToCheck = destination.Stride * Height;
                if (Format == 4)
                {
                    for (int i = 0; i < bytesToCheck; i += 4)
                    {
                        int pixel = i / 4;
                        destPtr[i + 3] = 255;
                        destPtr[i + 2] = RedPixels[pixel];
                        destPtr[i + 1] = GreenPixels[pixel];
                        destPtr[i] = BluePixels[pixel];
                    }
                }
                else
                {
                    int padding = destination.Stride - Width * 3;
                    bytesToCheck = Width * 3 * Height;
                    for (int i = 0; i < bytesToCheck; i += 3)
                    {
                        if (i != 0 && i % Width * 3 == 0)
                        {
                            i += padding;
                        }
                        int pixel = i / 3;
                        destPtr[i] = RedPixels[pixel];
                        destPtr[i + 1] = GreenPixels[pixel];
                        destPtr[i + 2] = BluePixels[pixel];
                    }
                }
            }

            localBitmap.UnlockBits(destination);
            return this;
        }

        /*
         * Function written by: Martijn Koopman
         * */

        /// <summary>
        /// <para>Cuts away the outer black areas in the RGB arrays </para>
        /// <para>This function will leave white pixels intact</para>
        /// <para>Does NOT use the Local image but only the RGB arrays </para>
        /// <para>And does NOT update the Local image AND RGB arrays</para>
        /// </summary>
        /// <returns>New Cropped VImage</returns>
        public VImage BoundingCrop()
        {
            // Calculate min_x
            int min_x = -1, max_x = -1, min_y = -1, max_y = -1;

            // Calculate min_y
            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    if (RedPixels[y * Width + x] < 50)
                    {
                        min_y = y;
                        break;
                    }
                }
                if (min_y != -1)
                    break;
            }

            // Is picture empty? return self
            if (min_y < 0)
                return this;

            // Calculate min_x
            for (int x = 0; x < Width; x++)
            {
                for (int y = Height - 1; y >= min_y; y--)
                {
                    if (RedPixels[y * Width + x] < 50)
                    {
                        min_x = x;
                        break;
                    }
                }
                if (min_x != -1)
                    break;
            }

            // Calculate max_y
            for (int y = Height - 1; y >= min_y; y--)
            { // Hier had je < min_y staan
                for (int x = min_x; x < Width; x++)
                {
                    if (RedPixels[y * Width + x] < 50)
                    {
                        max_y = y;
                        break;
                    }
                }
                if (max_y != -1)
                    break;
            }

            // Calculate max_x
            for (int x = Width - 1; x >= min_x; x--)
            {
                for (int y = max_y; y >= min_y; y--)
                {
                    if (RedPixels[y * Width + x] < 50)
                    {
                        max_x = x;
                        break;
                    }
                }
                if (max_x != -1)
                    break;
            }

            //Debug.WriteLine("Crop on (" + min_x + ", " + min_y + ") to (" + max_x + ", " + max_y + ")");
            return Crop(min_x, min_y, max_x, max_y);
        }

        /*
         * Function written by: Duncan Jenkins
         * */

        /// <summary>
        /// <para>Cuts out the area specified and puts it in a new bitmap</para>
        /// <para>Does NOT update the Local image AND RGB arrays</para>
        /// </summary>
        /// <param name="minX">leftmost X position</param>
        /// <param name="minY">upper Y position</param>
        /// <param name="maxX">rightmost X position</param>
        /// <param name="maxY">lower Y position</param>
        /// <returns>New cropped VImage object</returns>
        public VImage Crop(int minX, int minY, int maxX, int maxY)
        {
            // Construct new VImage

            int newWidth = maxX - minX + 1;
            int newHeight = maxY - minY + 1;
            VImage croppedImage = new VImage(newWidth, newHeight);

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    if (y >= minY && y <= maxY)
                    {
                        if (x >= minX && x <= maxX)
                        {
                            try
                            {
                                int sourceIndex = y * Width + x;
                                int destIndex = (y - minY) * newWidth + (x - minX);
                                croppedImage.RedPixels[destIndex] = RedPixels[sourceIndex];
                                croppedImage.GreenPixels[destIndex] = GreenPixels[sourceIndex];
                                croppedImage.BluePixels[destIndex] = BluePixels[sourceIndex];
                            }
                            catch (Exception e)
                            {
                                Debug.WriteLine(e.Message);
                                Debug.WriteLine(e.InnerException);
                            }
                        }
                    }
                }
            }
            return croppedImage.ApplyPixelChanges();
        }

        public VImage BlobCrop(int minX, int minY, int maxX, int maxY)
        {
            // Construct new VImage
            if (minX < 0) minX = 0;
            if (minY < 0) minY = 0;
            if (maxX > Width) maxX = Width;
            if (maxY > Height) maxY = Height;

            int newWidth = maxX - minX;
            //if (newWidth < 0) newWidth = 0;
            //else if (newWidth > Width) newWidth = Width;

            int newHeight = maxY - minY;
            //if (newHeight < 0) newHeight = 0;
            //if (newHeight > Height) newHeight = Height; 
            
            VImage croppedImage = new VImage(newWidth, newHeight);
            int newy = 0;
            int newx = 0;
            for (int y = minY; y < maxY; y++)
            {
                for (int x = minX; x < maxX; x++)
                {
                    int sourceIndex = y * Width + x;
                    int destIndex = newy * newWidth + newx;
                    croppedImage.RedPixels[destIndex] = RedPixels[sourceIndex];
                    croppedImage.GreenPixels[destIndex] = GreenPixels[sourceIndex];
                    croppedImage.BluePixels[destIndex] = BluePixels[sourceIndex];
                    newx++;
                }
                newy++;
                newx = 0;
            }                
            
            return croppedImage;
        }
        /*
         * Function written by: Duncan Jenkins
         * */

        /// <summary>
        /// <para>Will Crop the image and will binarize the image immediatly</para>
        /// <para>The threshold is 50</para>
        /// <para>Any grayvalue below 50 will be black the rest will be white</para>
        /// </summary>
        /// <param name="minX">leftmost X position</param>
        /// <param name="minY">upper Y position</param>
        /// <param name="maxX">rightmost X position</param>
        /// <param name="maxY">lower Y position</param>
        /// <returns>New VImage object containing the binary cropped image</returns>
        public VImage BinaryCrop(int minX, int minY, int maxX, int maxY)
        {
            // Construct new VImage
            int newWidth = maxX - minX + 1;
            int newHeight = maxY - minY + 1;
            VImage croppedImage = new VImage(newWidth, newHeight);

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    if (y >= minY && y <= maxY)
                    {
                        if (x >= minX && x <= maxX)
                        {
                            try
                            {
                                int sourceIndex = y * Width + x;
                                int destIndex = (y - minY) * newWidth + (x - minX);
                                if (GrayPixels[sourceIndex] < 50)
                                {
                                    croppedImage.RedPixels[destIndex] = 0;
                                    croppedImage.GreenPixels[destIndex] = 0;
                                    croppedImage.BluePixels[destIndex] = 0;
                                }
                                else
                                {
                                    croppedImage.RedPixels[destIndex] = 255;
                                    croppedImage.GreenPixels[destIndex] = 255;
                                    croppedImage.BluePixels[destIndex] = 255;
                                }
                            }
                            catch (Exception e)
                            {
                                Debug.WriteLine(e.Message);
                                Debug.WriteLine(e.InnerException);
                            }
                        }
                    }
                }
            }

            croppedImage.ApplyPixelChanges();
            return croppedImage;
        }

        /*
         * Function written by: Duncan Jenkins
         * */
        /// <summary>
        /// <para>Scales the bitmap to the new width and height</para>
        /// <para>local image and RGB arrays will not be changed</para>
        /// </summary>
        /// <param name="width">new width of the image</param>
        /// <param name="height">new height of the image</param>
        /// <returns>new VImage object containing the scaled image</returns>
        public VImage Scale(int width, int height)
        {
            Bitmap image = new Bitmap(width, height);

            // Use a graphics object to draw the scaled image
            using (Graphics graphics = Graphics.FromImage(image))
            {
                // Use High Quality settings
                graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                // Draw the new image
                graphics.DrawImage(localBitmap, 0, 0, image.Width, image.Height);
            }
            return new VImage(image);
        }

        /*
         * Function written by: Roy Scheefhals
         * */

        /// <summary>
        /// <para>Creates new bitmap using a grayvalues byte array</para>
        /// <para>Note that the size of the grayvalues has to be the size </para>
        /// <para>width * height of the VImage object its called on</para>
        /// <para>The Alpha value is preset to 255</para>
        /// <para>Function will output the grayvalues incorrectly if another size is used</para>
        /// </summary>
        /// <param name="grayvalues">byte array containing the grayvalues</param>
        /// <returns>new Bitmap filled with the grayvalues</returns>
        public Bitmap returnNewBitmapOutput(byte[] grayvalues)
        {
            BitmapData outputData = localBitmap.LockBits(new Rectangle(0, 0, localBitmap.Width, localBitmap.Height), ImageLockMode.ReadWrite, localBitmap.PixelFormat);
            int yrange = outputData.Height;
            int xrange = outputData.Stride / 4;
            unsafe
            {
                uint* outputPtr = (uint*)outputData.Scan0;

                for (int y = 0; y < yrange; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)grayvalues[(y * xrange) + x] << 16 | (byte)grayvalues[(y * xrange) + x] << 8 | (byte)grayvalues[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }
            }
            localBitmap.UnlockBits(outputData);
            return localBitmap;
        }

        /*
         * Function written by: Roy Scheefhals
         */

        /// <summary>
        /// <para>Creates new bitmap using the RGB byte arrays</para>
        /// <para>Note that the size of these arrays have to be the size </para>
        /// <para>width * height of the VImage object its called on</para>
        /// <para>The Alpha value is preset to 255</para>
        /// <para>Function will output the RGB values incorrectly if another size is used</para>
        /// </summary>
        /// <param name="Rchannel">byte array containing the Red channel of the image</param>
        /// <param name="Gchannel">byte array containing the Green channel of the image</param>
        /// <param name="Bchannel">byte array containing the Blue channel of the image</param>
        /// <returns>new Bitmap filled with the RGB values</returns>
        public Bitmap returnNewBitmapColour(byte[] Rchannel, byte[] Gchannel, byte[] Bchannel)
        {
            BitmapData outputData = localBitmap.LockBits(new Rectangle(0, 0, localBitmap.Width, localBitmap.Height), ImageLockMode.ReadWrite, localBitmap.PixelFormat);
            int yrange = outputData.Height;
            int xrange = outputData.Stride / 4;
            unsafe
            {
                uint* outputPtr = (uint*)outputData.Scan0;

                for (int y = 0; y < yrange; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)Rchannel[(y * xrange) + x] << 16 | (byte)Gchannel[(y * xrange) + x] << 8 | (byte)Bchannel[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }
            }
            localBitmap.UnlockBits(outputData);
            return localBitmap;
        }

        /// <summary>
        /// Function That restores multiple arrays into 1 full array
        /// </summary>
        /// <param name="bytearrays">Array of byteArrays that has to be 1 array</param>
        /// <returns>The multiple bytearrays as 1 array</returns>
        public byte[] restorePartialArraysToFullArray(byte[][] bytearrays, int rowDivider, int restRow)
        {

            byte[] newarray = new byte[bytearrays[bytearrays.Length - 1].Length]; // take the length of the last array
            int divider = rowDivider * Width;
            int restrow = restRow * Width;

            int newarIndex = 0;
            for (int j = 0; j < bytearrays.Length; j++)
            {
                byte[] curarray = bytearrays[j];
                for (int i = 0; i < divider; i++)
                {
                    newarIndex++;
                    newarray[newarIndex] = curarray[newarIndex];
                }
                newarIndex--;
                if (j == bytearrays.Length - 1)
                {
                    for (int i = 0; i < restrow; i++)
                    {
                        newarIndex++;
                        newarray[newarIndex] = curarray[newarIndex];
                    }
                }
            }
            return newarray;
        }

        #region Obsolete
        /*
         * Function written by: Roy Scheefhals
         */
        /*
        /// <summary>
        /// <para>Creates image of 4 separated gray arrays</para>
        /// <para>Note that the size of these arrays have to be the size </para>
        /// <para>width * height of the VImage object its called on</para>
        /// <para>The Alpha value is preset to 255</para>
        /// <para>Function will output the RGB values incorrectly if another size is used</para>
        /// </summary>
        /// <param name="channel1">First quarter of the gray array</param>
        /// <param name="channel2">Second quarter of the gray array</param>
        /// <param name="channel3">Third quarter of the gray array</param>
        /// <param name="channel4">Fourth quarter of the gray array</param>
        /// <returns>new Bitmap containing the grayvalues from the input</returns>
        public Bitmap returnNewBitmapFromHalfArrays(byte[] channel1, byte[] channel2, byte[] channel3, byte[] channel4)
        {
            BitmapData outputData = localImage.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, localImage.PixelFormat);
            int yrange1 = outputData.Height / 4;
            int yrange2 = outputData.Height / 2;
            int yrange3 = yrange1 + yrange2;
            int yrange4 = outputData.Height;
            int xrange = outputData.Stride / 4;
            int y;
            unsafe
            {
                uint* outputPtr = (uint*)outputData.Scan0;

                for (y = 0; y < yrange1; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)channel1[(y * xrange) + x] << 16 | (byte)channel1[(y * xrange) + x] << 8 | (byte)channel1[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }

                for (; y < yrange2; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)channel2[(y * xrange) + x] << 16 | (byte)channel2[(y * xrange) + x] << 8 | (byte)channel2[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }
                for (; y < yrange3; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)channel3[(y * xrange) + x] << 16 | (byte)channel3[(y * xrange) + x] << 8 | (byte)channel3[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }
                for (; y < yrange4; y++)
                {
                    for (int x = 0; x < xrange; x++)
                    {
                        uint input = (uint)((byte)255 << 24 | (byte)channel4[(y * xrange) + x] << 16 | (byte)channel4[(y * xrange) + x] << 8 | (byte)channel4[(y * xrange) + x]);
                        outputPtr[(y * xrange) + x] = input;
                    }
                }
            }
            localImage.UnlockBits(outputData);
            return localImage;
        }*/
        #endregion

    }
}
